feat: stage pod template rolls behind cluster permit - #338
Conversation
Operator image upgrades that change the ValkeyNode pod template were rewriting every 1-pod StatefulSet in one reconcile wave, taking the whole cluster down at once. Gate cluster-owned StatefulSet/Deployment template updates on valkey.io/allow-workload-revision (granted one node at a time by the ValkeyCluster controller, replicas first with proactive failover). Surface WorkloadDrift while waiting. Own API-server defaults so residual default noise does not re-trigger rolls. Spec and workload grants share a single in-flight permit. Standalone ValkeyNodes still apply immediately. Create path unchanged. Signed-off-by: daanvinken <daanvinken@tythus.com>
CI Check formatting failed on third-party vs local import grouping. Signed-off-by: daanvinken <daanvinken@tythus.com>
|
| Filename | Overview |
|---|---|
| internal/controller/valkeynode_controller.go | Adds permit-gated workload updates and repairs the prior early-return issue by synchronizing non-template workload state when templates match. |
| internal/controller/valkeycluster_controller.go | Extends replica-first, failover-aware node reconciliation to grant and serialize workload rollout permits. |
| internal/controller/workload_roll.go | Introduces stable template comparison and shared helpers for desired revisions, permits, and in-flight rollout state. |
| internal/controller/valkeynode_resources.go | Mirrors additional API-server probe defaults to keep desired and persisted pod templates stable. |
| api/v1alpha1/valkeynode_types.go | Adds the WorkloadDrift condition and AwaitingRollPermit reason vocabulary. |
Sequence Diagram
sequenceDiagram
participant CC as ValkeyCluster Controller
participant API as Kubernetes API
participant NC as ValkeyNode Controller
participant WL as StatefulSet/Deployment
CC->>API: Reconcile ValkeyNode spec
NC->>WL: Compare desired and live templates
alt Template matches
NC->>WL: Sync labels, owner, and non-template spec
else Cluster-owned template drift
NC->>API: Set desired revision and WorkloadDrift
CC->>API: Grant one-node rollout permit
NC->>WL: Apply permitted template update
NC->>API: Clear drift after apply
NC->>WL: Poll rollout completion
NC->>API: Clear permit when rollout completes
else Standalone node
NC->>WL: Apply template update immediately
end
Reviews (3): Last reviewed commit: "fix: satisfy lint and heal non-template ..." | Re-trigger Greptile
Drop unused anyNodeHasInFlightWorkloadRoll. Split reconcileValkeyNode helpers so gocyclo stays under the limit. When the pod template already matches, still sync labels, owner, and Spec so non-template controller fields do not go stale. Signed-off-by: daanvinken <daanvinken@tythus.com>
jdheyburn
left a comment
There was a problem hiding this comment.
I did a quick scan on the review, but I don't know if I'm sold on the design yet. I would rather understand what is causing the ValkeyCluster controller to push the change out to so many nodes at once. I spent a bit of time on the original PRs to implement safe sequential rolls, so I'm interested to see what's happening.
|
|
||
| // syncDeploymentWithoutRoll updates labels, owner, and Spec when the pod | ||
| // template would not roll. | ||
| func (r *ValkeyNodeReconciler) syncDeploymentWithoutRoll(ctx context.Context, node *valkeyiov1alpha1.ValkeyNode, dep *appsv1.Deployment, desired *appsv1.Deployment) error { |
There was a problem hiding this comment.
There's a lot of similarity with syncStatefulSetWithoutRoll. Are we able to DRY these? Something like syncWorkloadWithoutRoll, that might help cut the LOC down.
There was a problem hiding this comment.
Fair! We can for sure
|
this is the right extension. the sequencing the cluster does at the CR level was being undone the moment each node rewrote its own template, so gating the template apply behind the same permit closes a real gap. and it's good that it also composes well with #317: now that the operator owns the api-server defaults, the template only differs on genuine changes (image, real builder changes), so this stages the changes that should be staged rather than defaulting churn. the two together are the full fix for #337. one thing to make sure is observable: the workload permit is a single cluster-wide token, so a node whose new pod never becomes Ready (bad image, failing probe) holds it indefinitely and no other node's workload roll proceeds. that's the safe behaviour, you don't want to cascade a broken rollout, but it means one stuck node wedges every other node's template roll silently. the (the DRY point on |
|
Let's take the discussion on the bug itself to #337 (comment) |
This PR closes #337
Summary
ValkeyCluster already updates ValkeyNode specs carefully: one node at a time, replicas before primaries, with a failover attempt before rolling a primary.
That care stopped at the ValkeyNode object. Each node rewrites its single-pod StatefulSet (or Deployment) as soon as the computed pod template differs, with no coordination across nodes. Image changes and operator upgrades that rewrite the built template could restart every pod in the cluster at once.
This PR extends the same staged idea to the pod template apply path.
Features / Behaviour Changes
valkey.io/allow-workload-revision).WorkloadDriftcondition and records the desired template revision for the cluster controller to grant.Implementation
workload_roll.go: hash the desired pod template, read/write permit annotations, decide grant vs wait, shared in-flight check used by both Spec and workload paths.WorkloadDrift, leave the live workload alone, and requeue.ValkeyNodeConditionWorkloadDrifton the ValkeyNode API for visibility while waiting.Review focus: permit grant/clear transitions, the shared in-flight check when
allow=*is used for Spec rolls, and that waiters do not thrash events on every requeue.Limitations
*for Spec-driven rolls where the cluster does not know the hash yet.Testing
go test ./internal/controller/passes.Checklist
pre-commit run --all-filesor hooks on commit)